#!/usr/bin/env python
# *-* encoding: utf8
# 
# Copyright (c) 2006 Stian Soiland
# 
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
#
# Author: Stian Soiland <stian@soiland.no>
# URL: http://soiland.no/i/src/
# License: LGPL
#
"""File system API.

Early prototype of file system API for usage by PerFS.  This is almost
abstract implementation which just stores in memory.
"""



import uuid
import datetime
from StringIO import StringIO

class Filesystem(object):
    def __init__(self, root):
        self.root = root

    def open(self, path, mode="r"):
        try:
            resource = self.root.get(path)
        except KeyError, e:
            raise IOError(2, "No such file or directory: %r" % path)     
        f = resource.open(mode)
        return f
    
    def info(self, path):
        return self.root.get(path)     


class Resource(object):
    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent
        self.creationdate = datetime.datetime.now()
        self.lastmodified = self.creationdate
        self.identifier = uuid.UUID().str
        if parent:
            parent.add(name, self)
            

    def _updated(self):          
        self.lastmodified = datetime.datetime.now()
    
    # Ignore the rest of the path by default
    def get(self, path):
        return self
   
    def __str__(self):
        return self.path() or "/"
    
    def __repr__(self):
        return "<%s %s>" % (self.__class__.__name__, self)    
    

class Directory(Resource):
    contenttype = "httpd/unix-directory"
    def __init__(self, name, parent=None):
        super(Directory, self).__init__(name, parent) 
        self.children = {}
    
    def add(self, name, resource):
        # FIXME: Make thread safe
        if name in self.children:
            # FIXME> Real exceptions
            raise AttributeError, "Already exist %s" % name
        self.children[name] = resource     
        self._updated()
    
    def remove(self, name):
        # FIXME: Make thread safe
        obj = self.children[name]
        if obj.parent == self:
            obj.parent == None
        del self.children[name]   
        self._updated()
                
    def get(self, path):
        if "/" in path:
            child, rest = path.split("/", 1)    
        else:
            child, rest = path, None
        if not child or child == ".":
            entry = self
        elif child == "..":
            entry = self.parent or self
        else:
            entry = self.children[child]      
        if not rest:    
            return entry
        return entry.get(rest)    
   
    def open(self, mode=None):     
        raise IOError(21, "Is a directory")

    def path(self):
        if not self.parent:
            return "/"
        return self.parent.path().rstrip("/") + "/" + self.name
    
    def root(self):
        if not self.parent:
            return self    
        return self.parent.root()    
            

class File(Resource):
    def __init__(self, name, parent=None):
        super(File, self).__init__(name, parent) 
        self.contenttype = "text/plain"
        self.data = ""

    def _get_data(self):
        return self._data     
    def _set_data(self, data):
        self._data = data
        self._updated()
    data = property(_get_data, _set_data)    
        
    def open(self, mode="r"):
        return FileObject(self, mode)
    
    @property
    def size(self):
        return len(self.data)

class FileObject(StringIO, object):
    def __init__(self, file, mode):
        super(FileObject, self).__init__(file.data)
        self.changed = False    
        # Before we have checked modes, nothing allowed
        self.readable = self.writable = False
        self.file = file
        self.mode = mode
        # Check mode 
        if "w" in mode:
            self.writable = True
            self.truncate()
        if "a" in mode:
            self.writable = True
        if "r" in mode:
            self.readable = True    
        if "+" in mode:
            self.writable = self.readable = True
        if "a" in mode:
            # Seek to the end of the file
            self.seek(0,2)    
   
    def _readable(method):
        """Decorator that wraps the given method to make sure
        the file is opened as readable."""
        def wrapper(self, *args, **kwargs):               
            if not self.readable:
                raise IOError(9, "Bad file descriptor")     
            return method(self, *args, **kwargs)    
        wrapper.func_name = method.func_name 
        wrapper.__doc__ = method.__doc__
        return wrapper

    def _writable(method=None, set_changed=True):
        """Decorator that wraps the given method to make sure
        the file is opened as writable. Optional parameter
        set_changed can be set to False to avoid updating
        self.changed.
        
        Thus this decorator can be used as either @_writable
        or @_writable(set_changed=False)
        """
        def decorator(method=method):
            def wrapper(self, *args, **kwargs):               
                if not self.writable:
                    raise IOError(9, "Bad file descriptor")     
                # Even if it might throw an exception, we 
                # don't know if it has changed or not
                # FIXME: flush() will set changed=True ! 
                if set_changed:
                    self.changed = True    
                return method(self, *args, **kwargs)    
            wrapper.func_name = method.func_name 
            wrapper.__doc__ = method.__doc__
            return wrapper
        # ARGHSS! To support both @_writable and _writable(False)    
        if method:
            return decorator()    
        else:    
            return decorator        

   
    # We'll just wrap these methods straight from our 
    # super class
    read = _readable(StringIO.read)
    readline = _readable(StringIO.readline)
    readlines = _readable(StringIO.readlines)
    __iter__ = _readable(StringIO.__iter__)
    next = _readable(StringIO.next)
                  
    truncate = _writable(StringIO.truncate)              
    write = _writable(StringIO.write)
    writelines = _writable(StringIO.writelines)
                  
    @_writable(set_changed=False)
    def flush(self):                
        """Flush written data back to original store"""
        if self.changed:
            self.file.data = self.getvalue()
                  
    def close(self):
        """Close the file object, flush if needed."""
        if self.writable:
            self.flush()
        super(FileObject, self).close()               
    
    def __del__(self):
        """Close the file on destruction. 
        Note that the GC behaviour is not deterministic, so don't rely
        on this to store your data when writing to the file."""
        try:
            self.close()    
        except:
            pass    
        

def dummies():
    root = Directory("")
    fs = Filesystem(root)
    pub = Directory("pub", root) 
    etc = Directory("etc", root) 
    File("passwd", etc)
    hello = File("hello.txt", pub)
    hello.open("w").write("Hello world\n")
    return fs

        
